/******************************************************************************* * Copyright (c) 2000, 2008 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.corext.refactoring.generics; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.ArrayType; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.HierarchyType; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.TType; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.TypeEnvironment; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.typesets.EnumeratedTypeSet; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.typesets.SingletonTypeSet; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.typesets.TypeSet; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.typesets.TypeSetEnvironment; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ArrayElementVariable2; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ArrayTypeVariable2; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.CastVariable2; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.CollectionElementVariable2; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ConstraintVariable2; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.ITypeConstraint2; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.IndependentTypeVariable2; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.TTypes; import org.eclipse.jdt.internal.corext.refactoring.typeconstraints2.TypeEquivalenceSet; public class InferTypeArgumentsConstraintsSolver { private static class TTypeComparator implements Comparator { public int compare(Object o1, Object o2) { return ((TType) o1).getPrettySignature().compareTo(((TType) o2).getPrettySignature()); } public static TTypeComparator INSTANCE= new TTypeComparator(); } private final static String CHOSEN_TYPE= "chosenType"; //$NON-NLS-1$ private final InferTypeArgumentsTCModel fTCModel; private TypeSetEnvironment fTypeSetEnvironment; /** * The work-list used by the type constraint solver to hold the set of * nodes in the constraint graph that remain to be (re-)processed. Entries * are <code>ConstraintVariable2</code>s. */ private LinkedList/*<ConstraintVariable2>*/ fWorkList; private InferTypeArgumentsUpdate fUpdate; public InferTypeArgumentsConstraintsSolver(InferTypeArgumentsTCModel typeConstraintFactory) { fTCModel= typeConstraintFactory; fWorkList= new LinkedList(); } public InferTypeArgumentsUpdate solveConstraints(IProgressMonitor pm) { pm.beginTask("", 2); //$NON-NLS-1$ fUpdate= new InferTypeArgumentsUpdate(); ConstraintVariable2[] allConstraintVariables= fTCModel.getAllConstraintVariables(); if (allConstraintVariables.length == 0) return fUpdate; fTypeSetEnvironment= new TypeSetEnvironment(fTCModel.getTypeEnvironment()); ParametricStructureComputer parametricStructureComputer= new ParametricStructureComputer(allConstraintVariables, fTCModel); Collection/*<CollectionElementVariable2>*/ newVars= parametricStructureComputer.createElemConstraintVariables(); ArrayList newAllConstraintVariables= new ArrayList(); newAllConstraintVariables.addAll(Arrays.asList(allConstraintVariables)); newAllConstraintVariables.addAll(newVars); allConstraintVariables= (ConstraintVariable2[]) newAllConstraintVariables.toArray(new ConstraintVariable2[newAllConstraintVariables.size()]); //loop over all TypeEquivalenceSets and unify the elements from the fElemStructureEnv with the existing TypeEquivalenceSets HashSet allTypeEquivalenceSets= new HashSet(); for (int i= 0; i < allConstraintVariables.length; i++) { TypeEquivalenceSet typeEquivalenceSet= allConstraintVariables[i].getTypeEquivalenceSet(); if (typeEquivalenceSet != null) allTypeEquivalenceSets.add(typeEquivalenceSet); } for (Iterator iter= allTypeEquivalenceSets.iterator(); iter.hasNext();) { TypeEquivalenceSet typeEquivalenceSet= (TypeEquivalenceSet) iter.next(); ConstraintVariable2[] contributingVariables= typeEquivalenceSet.getContributingVariables(); for (int i= 0; i < contributingVariables.length; i++) { for (int j= i + 1; j < contributingVariables.length; j++) { ConstraintVariable2 first= contributingVariables[i]; ConstraintVariable2 second= contributingVariables[j]; fTCModel.createElementEqualsConstraints(first, second); // recursively } } } ITypeConstraint2[] allTypeConstraints= fTCModel.getAllTypeConstraints(); for (int i= 0; i < allTypeConstraints.length; i++) { ITypeConstraint2 typeConstraint= allTypeConstraints[i]; fTCModel.createElementEqualsConstraints(typeConstraint.getLeft(), typeConstraint.getRight()); } initializeTypeEstimates(allConstraintVariables); if (pm.isCanceled()) throw new OperationCanceledException(); fWorkList.addAll(Arrays.asList(allConstraintVariables)); runSolver(new SubProgressMonitor(pm, 1)); chooseTypes(allConstraintVariables, new SubProgressMonitor(pm, 1)); findCastsToRemove(fTCModel.getCastVariables()); return fUpdate; } private void initializeTypeEstimates(ConstraintVariable2[] allConstraintVariables) { for (int i= 0; i < allConstraintVariables.length; i++) { ConstraintVariable2 cv= allConstraintVariables[i]; //TODO: not necessary for types that are not used in a TypeConstraint but only as type in CollectionElementVariable //TODO: handle nested element variables; see ParametricStructureComputer.createAndInitVars() TypeEquivalenceSet set= cv.getTypeEquivalenceSet(); if (set == null) { set= new TypeEquivalenceSet(cv); set.setTypeEstimate(createInitialEstimate(cv)); cv.setTypeEquivalenceSet(set); } else { TypeSet typeEstimate= (TypeSet) cv.getTypeEstimate(); if (typeEstimate == null) { ConstraintVariable2[] cvs= set.getContributingVariables(); typeEstimate= fTypeSetEnvironment.getUniverseTypeSet(); for (int j= 0; j < cvs.length; j++) //TODO: optimize: just try to find an immutable CV; if not found, use Universe typeEstimate= typeEstimate.intersectedWith(createInitialEstimate(cvs[j])); set.setTypeEstimate(typeEstimate); } } } } private TypeSet createInitialEstimate(ConstraintVariable2 cv) { // TODO: check assumption: only immutable CVs have a type // ParametricStructure parametricStructure= fElemStructureEnv.elemStructure(cv); // if (parametricStructure != null && parametricStructure != ParametricStructureComputer.ParametricStructure.NONE) { // return SubTypesOfSingleton.create(parametricStructure.getBase()); // } TType type= cv.getType(); if (type == null) { return fTypeSetEnvironment.getUniverseTypeSet(); } else if (cv instanceof IndependentTypeVariable2) { return fTypeSetEnvironment.getUniverseTypeSet(); //TODO: solve problem with recursive bounds // TypeVariable tv= (TypeVariable) type; // TType[] bounds= tv.getBounds(); // TypeSet result= SubTypesOfSingleton.create(bounds[0].getErasure()); // for (int i= 1; i < bounds.length; i++) { // result= result.intersectedWith(SubTypesOfSingleton.create(bounds[i].getErasure())); // } // return result; } else if (cv instanceof ArrayTypeVariable2) { return fTypeSetEnvironment.getUniverseTypeSet(); } else if (cv instanceof ArrayElementVariable2) { if (cv.getType() != null && cv.getType().isTypeVariable()) { return fTypeSetEnvironment.getUniverseTypeSet(); } else { return new SingletonTypeSet(type, fTypeSetEnvironment); } } else if (type.isVoidType()) { return fTypeSetEnvironment.getEmptyTypeSet(); } else { return new SingletonTypeSet(type, fTypeSetEnvironment); } } private void runSolver(SubProgressMonitor pm) { pm.beginTask("", fWorkList.size() * 3); //$NON-NLS-1$ while (! fWorkList.isEmpty()) { // Get a variable whose type estimate has changed ConstraintVariable2 cv= (ConstraintVariable2) fWorkList.removeFirst(); List/*<ITypeConstraint2>*/ usedIn= fTCModel.getUsedIn(cv); processConstraints(usedIn, cv); pm.worked(1); if (pm.isCanceled()) throw new OperationCanceledException(); } pm.done(); } /** * Given a list of <code>ITypeConstraint2</code>s that all refer to a * given <code>ConstraintVariable2</code> (whose type bound has presumably * just changed), process each <code>ITypeConstraint</code>, propagating * the type bound across the constraint as needed. * * @param usedIn the <code>List</code> of <code>ITypeConstraint2</code>s * to process * @param changedCv the constraint variable whose type bound has changed */ private void processConstraints(List/*<ITypeConstraint2>*/ usedIn, ConstraintVariable2 changedCv) { int i= 0; for (Iterator iter= usedIn.iterator(); iter.hasNext(); i++) { ITypeConstraint2 tc= (ITypeConstraint2) iter.next(); maintainSimpleConstraint(changedCv, tc); //TODO: prune tcs which cannot cause further changes // Maybe these should be pruned after a special first loop over all ConstraintVariables, // Since this can only happen once for every CV in the work list. // if (isConstantConstraint(stc)) // fTypeConstraintFactory.removeUsedIn(stc, changedCv); } } private void maintainSimpleConstraint(ConstraintVariable2 changedCv, ITypeConstraint2 stc) { ConstraintVariable2 left= stc.getLeft(); ConstraintVariable2 right= stc.getRight(); TypeEquivalenceSet leftSet= left.getTypeEquivalenceSet(); TypeEquivalenceSet rightSet= right.getTypeEquivalenceSet(); TypeSet leftEstimate= (TypeSet) leftSet.getTypeEstimate(); TypeSet rightEstimate= (TypeSet) rightSet.getTypeEstimate(); if (leftEstimate.isUniverse() && rightEstimate.isUniverse()) return; // nothing to do if (leftEstimate.equals(rightEstimate)) return; // nothing to do TypeSet lhsSuperTypes= leftEstimate.superTypes(); TypeSet rhsSubTypes= rightEstimate.subTypes(); if (! rhsSubTypes.containsAll(leftEstimate)) { TypeSet xsection= leftEstimate.intersectedWith(rhsSubTypes); // if (xsection.isEmpty()) // too bad, but this can happen // throw new IllegalStateException("Type estimate set is now empty for LHS in " + left + " <= " + right + "; estimates were " + leftEstimate + " <= " + rightEstimate); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ leftSet.setTypeEstimate(xsection); fWorkList.addAll(Arrays.asList(leftSet.getContributingVariables())); } if (! lhsSuperTypes.containsAll(rightEstimate)) { TypeSet xsection= rightEstimate.intersectedWith(lhsSuperTypes); // if (xsection.isEmpty()) // throw new IllegalStateException("Type estimate set is now empty for RHS in " + left + " <= " + right + "; estimates were " + leftEstimate + " <= " + rightEstimate); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ rightSet.setTypeEstimate(xsection); fWorkList.addAll(Arrays.asList(rightSet.getContributingVariables())); } } private void chooseTypes(ConstraintVariable2[] allConstraintVariables, SubProgressMonitor pm) { pm.beginTask("", allConstraintVariables.length); //$NON-NLS-1$ for (int i= 0; i < allConstraintVariables.length; i++) { ConstraintVariable2 cv= allConstraintVariables[i]; TypeEquivalenceSet set= cv.getTypeEquivalenceSet(); if (set == null) continue; //TODO: should not happen iff all unused constraint variables got pruned //TODO: should calculate only once per EquivalenceRepresentative; can throw away estimate TypeSet afterwards TType type= chooseSingleType((TypeSet) cv.getTypeEstimate()); //TODO: is null for Universe TypeSet setChosenType(cv, type); if (cv instanceof CollectionElementVariable2) { CollectionElementVariable2 elementCv= (CollectionElementVariable2) cv; fUpdate.addDeclaration(elementCv); } pm.worked(1); if (pm.isCanceled()) throw new OperationCanceledException(); } pm.done(); } private TType chooseSingleType(TypeSet typeEstimate) { if (typeEstimate.isUniverse() || typeEstimate.isEmpty()) { return null; } else if (typeEstimate.hasUniqueLowerBound()) { return typeEstimate.uniqueLowerBound(); } else { EnumeratedTypeSet lowerBound= typeEstimate.lowerBound().enumerate(); ArrayList/*<TType>*/ interfaceCandidates= null; for (Iterator iter= lowerBound.iterator(); iter.hasNext();) { TType type= (TType) iter.next(); if (! type.isInterface()) { return type; } else { if (interfaceCandidates == null) interfaceCandidates= new ArrayList(2); interfaceCandidates.add(type); } } if (interfaceCandidates == null || interfaceCandidates.size() == 0) { return null; } else if (interfaceCandidates.size() == 1) { return (TType) interfaceCandidates.get(0); } else { ArrayList nontaggingCandidates= getNonTaggingInterfaces(interfaceCandidates); if (nontaggingCandidates.size() != 0) { return (TType) Collections.min(nontaggingCandidates, TTypeComparator.INSTANCE); } else { return (TType) Collections.min(interfaceCandidates, TTypeComparator.INSTANCE); } } } } private static final int MAX_CACHE= 1024; private Map/*<TType, Boolean>*/ fInterfaceTaggingCache= new LinkedHashMap(MAX_CACHE, 0.75f, true) { private static final long serialVersionUID= 1L; protected boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_CACHE; } }; private ArrayList getNonTaggingInterfaces(ArrayList interfaceCandidates) { ArrayList unresolvedTypes= new ArrayList(); ArrayList nonTagging= new ArrayList(); for (int i= 0; i < interfaceCandidates.size(); i++) { TType interf= (TType) interfaceCandidates.get(i); Object isTagging= fInterfaceTaggingCache.get(interf); if (isTagging == null) unresolvedTypes.add(interf); else if (isTagging == Boolean.FALSE) nonTagging.add(interf); } if (unresolvedTypes.size() != 0) { TType[] interfaces= (TType[]) unresolvedTypes.toArray(new TType[unresolvedTypes.size()]); HierarchyType firstInterface= (HierarchyType) interfaces[0]; IJavaProject javaProject= firstInterface.getJavaElementType().getJavaProject(); ITypeBinding[] interfaceBindings= TypeEnvironment.createTypeBindings(interfaces, javaProject); //expensive... for (int i= 0; i < interfaceBindings.length; i++) { if (interfaceBindings[i].getDeclaredMethods().length == 0) { fInterfaceTaggingCache.put(interfaces[i], Boolean.TRUE); } else { fInterfaceTaggingCache.put(interfaces[i], Boolean.FALSE); nonTagging.add(interfaces[i]); } } } return nonTagging; } private void findCastsToRemove(CastVariable2[] castVariables) { for (int i= 0; i < castVariables.length; i++) { CastVariable2 castCv= castVariables[i]; ConstraintVariable2 expressionVariable= castCv.getExpressionVariable(); TType chosenType= InferTypeArgumentsConstraintsSolver.getChosenType(expressionVariable); TType castType= castCv.getType(); TType expressionType= expressionVariable.getType(); if (chosenType != null && TTypes.canAssignTo(chosenType, castType)) { if (chosenType.equals(expressionType)) continue; // The type has not changed. Don't remove the cast, since it could be // there to get access to default-visible members or to // unify types of conditional expressions. fUpdate.addCastToRemove(castCv); } else if (expressionVariable instanceof ArrayTypeVariable2 && castType.isArrayType()) { // bug 97258 ArrayElementVariable2 arrayElementCv= fTCModel.getArrayElementVariable(expressionVariable); if (arrayElementCv == null) continue; TType chosenArrayElementType= InferTypeArgumentsConstraintsSolver.getChosenType(arrayElementCv); if (chosenArrayElementType != null && TTypes.canAssignTo(chosenArrayElementType, ((ArrayType) castType).getComponentType())) { if (expressionType instanceof ArrayType && chosenArrayElementType.equals(((ArrayType) expressionType).getComponentType())) continue; // The type has not changed. Don't remove the cast, since it could be // there to unify types of conditional expressions. fUpdate.addCastToRemove(castCv); } } } } public static TType getChosenType(ConstraintVariable2 cv) { TType type= (TType) cv.getData(CHOSEN_TYPE); if (type != null) return type; TypeEquivalenceSet set= cv.getTypeEquivalenceSet(); if (set == null) { //TODO: should not have to set this here. Clean up when caching chosen type return null; // // no representative == no restriction // set= new TypeEquivalenceSet(cv); // set.setTypeEstimate(TypeUniverseSet.create()); // cv.setTypeEquivalenceSet(set); } return cv.getTypeEstimate().chooseSingleType(); } private static void setChosenType(ConstraintVariable2 cv, TType type) { cv.setData(CHOSEN_TYPE, type); } }